package org.hyperion.rs2.task.impl;

import java.util.Iterator;

import org.hyperion.rs2.GameEngine;
import org.hyperion.rs2.content.minigames.FightCaves;
import org.hyperion.rs2.model.Appearance;
import org.hyperion.rs2.model.ChatMessage;
import org.hyperion.rs2.model.Entity;
import org.hyperion.rs2.model.Item;
import org.hyperion.rs2.model.Location;
import org.hyperion.rs2.model.Player;
import org.hyperion.rs2.model.UpdateFlags;
import org.hyperion.rs2.model.World;
import org.hyperion.rs2.model.UpdateFlags.UpdateFlag;
import org.hyperion.rs2.model.container.Container;
import org.hyperion.rs2.model.container.Equipment;
import org.hyperion.rs2.model.container.Equipment.EquipmentType;
import org.hyperion.rs2.net.Packet;
import org.hyperion.rs2.net.PacketBuilder;
import org.hyperion.rs2.task.Task;
import org.hyperion.rs2.util.TextUtils;

/**
 * A task which creates and sends the player update block.
 * @author Graham Edgecombe
 *
 */
public class PlayerUpdateTask implements Task {
	
	/**
	 * The player.
	 */
	private Player player;
	
	/**
	 * Creates an update task.
	 * @param player The player.
	 */
	public PlayerUpdateTask(Player player) {
		this.player = player;
	}

	@Override
	public void execute(GameEngine context) {		
		/*
		 * If the map region changed send the new one.
		 * We do this immediately as the client can begin loading it before the
		 * actual packet is received.
		 */
		if(player.isMapRegionChanging()) {
			player.getActionSender().sendMapRegion();
		}
		
		/*
		 * The update block packet holds update blocks and is send after the
		 * main packet. 
		 */
		PacketBuilder updateBlock = new PacketBuilder();
		
		/*
		 * The main packet is written in bits instead of bytes and holds
		 * information about the local list, players to add and remove,
		 * movement and which updates are required.
		 */
		PacketBuilder packet = new PacketBuilder(42, Packet.Type.VARIABLE_SHORT);
		packet.startBitAccess();
		
		/*
		 * Updates this player.
		 */
		updateThisPlayerMovement(packet);
		updatePlayer(updateBlock, player, false, false);
		
		/*
		 * Write the current size of the player list.
		 */
		packet.putBits(8, player.getLocalPlayers().size());
		
		/*
		 * Iterate through the local player list.
		 */
		for(Iterator<Player> it$ = player.getLocalPlayers().iterator(); it$.hasNext();) {
			/*
			 * Get the next player.
			 */
			Player otherPlayer = it$.next();
						
			/*
			 * If the player should still be in our list.
			 */
			if(World.getWorld().getPlayers().contains(otherPlayer) && !otherPlayer.isTeleporting() && otherPlayer.getLocation().isWithinDistance(player.getLocation()) && !FightCaves.IN_CAVES.contains(otherPlayer)) {
				/*
				 * Update the movement.
				 */
				updatePlayerMovement(packet, otherPlayer);
				
				/*
				 * Check if an update is required, and if so, send the update.
				 */
				if(otherPlayer.getUpdateFlags().isUpdateRequired()) {
					updatePlayer(updateBlock, otherPlayer, false, false);
				}
			} else {
				/*
				 * Otherwise, remove the player from the list.
				 */
				it$.remove();
				
				/*
				 * Tell the client to remove the player from the list.
				 */
				packet.putBits(1, 1);
				packet.putBits(2, 3);
			}
		}
		
		/*
		 * Loop through every player.
		 */
		for(Player otherPlayer : World.getWorld().getRegionManager().getLocalPlayers(player)) {
			/*
			 * Check if there is room left in the local list.
			 */
			if(player.getLocalPlayers().size() >= 255) {
				/*
				 * There is no more room left in the local list. We cannot add
				 * more players, so we just ignore the extra ones. They will be
				 * added as other players get removed.
				 */
				break;
			}
			
			/*
			 * If they should not be added ignore them.
			 */
			if(otherPlayer == player || player.getLocalPlayers().contains(otherPlayer) || FightCaves.IN_CAVES.contains(otherPlayer)) {
				continue;
			}
			
			/*
			 * Add the player to the local list if it is within distance.
			 */
			player.getLocalPlayers().add(otherPlayer);
			
			/*
			 * Add the player in the packet.
			 */
			addNewPlayer(packet, otherPlayer);
			
			/*
			 * Update the player, forcing the appearance flag.
			 */
			updatePlayer(updateBlock, otherPlayer, true, false);
		}
		
		/*
		 * Check if the update block is not empty.
		 */
		if(!updateBlock.isEmpty()) {
			/*
			 * Write a magic id indicating an update block follows.
			 */
			packet.putBits(11, 2047);
			packet.finishBitAccess();
			
			/*
			 * Add the update block at the end of this packet.
			 */
			packet.put(updateBlock.toPacket().getPayload());
		} else {
			/*
			 * Terminate the packet normally.
			 */
			packet.finishBitAccess();
		}
		
		/*
		 * Write the packet.
		 */
		player.write(packet.toPacket());
	}

	/**
	 * Updates a non-this player's movement.
	 * @param packet The packet.
	 * @param otherPlayer The player.
	 */
	public void updatePlayerMovement(PacketBuilder packet, Player otherPlayer) {
		/*
		 * Check which type of movement took place.
		 */
		if(otherPlayer.getSprites().getPrimarySprite() == -1) {
			/*
			 * If no movement did, check if an update is required.
			 */
			if(otherPlayer.getUpdateFlags().isUpdateRequired()) {
				/*
				 * Signify that an update happened.
				 */
				packet.putBits(1, 1);
				
				/*
				 * Signify that there was no movement.
				 */
				packet.putBits(2, 0);
			} else {
				/*
				 * Signify that nothing changed.
				 */
				packet.putBits(1, 0);
			}
		} else if(otherPlayer.getSprites().getSecondarySprite() == -1) {
			/*
			 * The player moved but didn't run. Signify that an update is
			 * required.
			 */
			packet.putBits(1, 1);
			
			/*
			 * Signify we moved one tile.
			 */
			packet.putBits(2, 1);
			
			/*
			 * Write the primary sprite (i.e. walk direction).
			 */
			packet.putBits(3, otherPlayer.getSprites().getPrimarySprite());
			
			/*
			 * Write a flag indicating if a block update happened.
			 */
			packet.putBits(1, otherPlayer.getUpdateFlags().isUpdateRequired() ? 1 : 0);
		} else {
			/*
			 * The player ran. Signify that an update happened.
			 */
			packet.putBits(1, 1);
			
			/*
			 * Signify that we moved two tiles.
			 */
			packet.putBits(2, 2);
			
			/*
			 * Write the primary sprite (i.e. walk direction).
			 */
			packet.putBits(3, otherPlayer.getSprites().getPrimarySprite());
			
			/*
			 * Write the secondary sprite (i.e. run direction).
			 */
			packet.putBits(3, otherPlayer.getSprites().getSecondarySprite());
			
			/*
			 * Write a flag indicating if a block update happened.
			 */
			packet.putBits(1, otherPlayer.getUpdateFlags().isUpdateRequired() ? 1 : 0);
		}
	}

	/**
	 * Adds a new player.
	 * @param packet The packet.
	 * @param otherPlayer The player.
	 */
	public void addNewPlayer(PacketBuilder packet, Player otherPlayer) {
		/*
		 * Write the player index.
		 */
		packet.putBits(11, otherPlayer.getIndex());
		
		/*
		 * Write two flags here: the first indicates an update is required
		 * (this is always true as we add the appearance after adding a player)
		 * and the second to indicate we should discard client-side walk
		 * queues.
		 */
		packet.putBits(1, 1);
		packet.putBits(1, 1);
		packet.putBits(3, 1);
		
		/*
		 * Calculate the x and y offsets.
		 */
		int yPos = otherPlayer.getLocation().getY() - player.getLocation().getY();
		int xPos = otherPlayer.getLocation().getX() - player.getLocation().getX();
		
		/*
		 * Write the x and y offsets.
		 */
		packet.putBits(5, xPos);
		packet.putBits(5, yPos);

	}
	
    private static void appendHit2Update(final Player p, final PacketBuilder updateBlock) {
    	updateBlock.putByteC(p.getDamage().getHitDamage2());
    	updateBlock.putByteC(p.getDamage().getHitType2());
    	updateBlock.putByteS((byte) p.getSkills().getLevel(3));
    	updateBlock.put((byte) p.getSkills().getLevelForExperience(3));
    }

    private static void appendHitUpdate(final Player p, final PacketBuilder updateBlock) {
    	updateBlock.putByteA(p.getDamage().getHitDamage1());
    	updateBlock.putByteA(p.getDamage().getHitType1());
    	updateBlock.putByteC(p.getSkills().getLevel(3));
    	updateBlock.putByteC(p.getSkills().getLevelForExperience(3));
    }

	/**
	 * Updates a player.
	 * @param packet The packet.
	 * @param otherPlayer The other player.
	 * @param forceAppearance The force appearance flag.
	 * @param noChat Indicates chat should not be relayed to this player.
	 */
	public void updatePlayer(PacketBuilder packet, Player otherPlayer, boolean forceAppearance, boolean noChat) {
		/*
		 * If no update is required and we don't have to force an appearance
		 * update, don't write anything.
		 */
		if(!otherPlayer.getUpdateFlags().isUpdateRequired() && !forceAppearance) {
			return;
		}
		
		/*
		 * We can used the cached update block!
		 */
		synchronized(otherPlayer) {
			if(otherPlayer.hasCachedUpdateBlock() && otherPlayer != player && !forceAppearance && !noChat) {
				packet.put(otherPlayer.getCachedUpdateBlock().getPayload().flip());
				return;
			}
			
			/*
			 * We have to construct and cache our own block.
			 */
			PacketBuilder block = new PacketBuilder();
			
			/*
			 * Calculate the bitmask.
			 */
			int mask = 0;
			final UpdateFlags flags = otherPlayer.getUpdateFlags();
			
			if(flags.get(UpdateFlag.FACE_COORDINATE)) {
				mask |= 0x80;
			}
			if(flags.get(UpdateFlag.HIT)) {
				mask |= 0x200;
			}
			if(flags.get(UpdateFlag.FORCED_CHAT)) {
				mask |= 1;
			}
			if(flags.get(UpdateFlag.CHAT) && !noChat) {
				mask |= 4;
			}
			if(flags.get(UpdateFlag.HIT_2)) {
				mask |= 0x20;
			}
			if(flags.get(UpdateFlag.FACE_ENTITY)) {
				mask |= 0x10;
			}
			if(flags.get(UpdateFlag.ANIMATION)) {
				mask |= 0x40;
			}
			if(flags.get(UpdateFlag.GRAPHICS)) {
				mask |= 0x100;
			}
			if(flags.get(UpdateFlag.APPEARANCE) || forceAppearance) {
				mask |= 8;
			}
			/*
			if(flags.get(UpdateFlag.FORCE_MOVEMENT)) {
				mask |= 0x400;
			}*/
			
			/*
			 * Check if the bitmask would overflow a byte.
			 */
			if(mask >= 2) {
				/*
				 * Write it as a short and indicate we have done so.
				 */
				mask |= 2;
				block.put((byte) (mask & 0xFF));
				block.put((byte) (mask >> 8));
			} else {
				/*
				 * Write it as a byte.
				 */
				block.put((byte) (mask));
			}
			/*
			 * Append the appropriate updates.
			 */
			if(flags.get(UpdateFlag.FACE_COORDINATE)) {
				Location loc = otherPlayer.getFaceLocation();
				if(loc == null) {
					block.putShortA(0);
					block.putShortA(0);
				} else {
					block.putShortA(2 * loc.getX() + 1);
					block.putShortA(2 * loc.getY() + 1);
				}
			}
			if(flags.get(UpdateFlag.HIT)) {
				appendHitUpdate(otherPlayer, block);
			}
			if(flags.get(UpdateFlag.FORCED_CHAT)) {
				block.putString(otherPlayer.getForceChatText());
			}
			if(flags.get(UpdateFlag.CHAT) && !noChat) {
				appendChatUpdate(block, otherPlayer);
			}
			if(flags.get(UpdateFlag.HIT_2)) {
				appendHit2Update(otherPlayer, block);
			}
			if(flags.get(UpdateFlag.FACE_ENTITY)) {
				Entity entity = otherPlayer.getInteractingEntity();
				block.putLEShortA(entity == null ? -1 : entity.getClientIndex());
			}
			if(flags.get(UpdateFlag.ANIMATION)) {
				appendAnimationUpdate(block, otherPlayer);
			}
			if(flags.get(UpdateFlag.GRAPHICS)) {
				appendGraphicsUpdate(block, otherPlayer);
			}
			if(flags.get(UpdateFlag.APPEARANCE) || forceAppearance) {
				appendPlayerAppearanceUpdate(block, otherPlayer);
			}

			/*
			 * Convert the block builder to a packet.
			 */
			Packet blockPacket = block.toPacket();
			
			/*
			 * Now it is over, cache the block if we can.
			 */
			if(otherPlayer != player && !forceAppearance && !noChat) {
				otherPlayer.setCachedUpdateBlock(blockPacket);
			}
		
			/*
			 * And finally append the block at the end.
			 */
			packet.put(blockPacket.getPayload());
		}
	}
	
	/*
Force movement update mask (0x400) -    Yesterday, 12:58 AM
Thanks to Blake for making the topic which got me curious about this...it's the force movement mask (0x400 - the only one left for 317 RSPS to get).

This is the first update mask in the list, so it goes at the top (look how your others are done).

Code:

	public void appendForceMovement(stream str) {
		str.writeByteS(x1);
		str.writeByteS(y1);
		str.writeByteS(x2);
		str.writeByteS(y2);
		str.writeWordBigEndianA(speed1);
		str.writeWordA(speed2);
		str.writeByteS(direction);
	}

Now i will explain the variables.

As you can see, there are 2 sets of coordinate variables, x1/y1 and x2,y2, these refer to region coordinates (minimum of 0, maximum of 104), named currentX and currentY in any winterLove server.

x1 and y1 is the region x+y you will start from, if you are not at these coordinates, it will move to there (it will walk there, and will noclip if need be).

x2 and y2 is the region x+y you will finish at, your character will move from x1,y1 to x2,y2.

So lets use this as an example.
Code:

currentX = 50; // X where we are standing
currentY = 50; // Y where we are standing
x1 = currentX; // Start from our current position
y1 = currentY; // Start from our current position
x2 = currentX + 4; // Move 4 squares east.
y2 = currentY // stay in the same Y coordinate (we won't move north or south);

So now the mask knows that we want to move 4 squares east, i will explain the speed variables now.

speed1 is the speed you will move from your current coordinates to x1/y1 we specified earlier.

speed2, if you haven't guessed yet, is the same as above, but the speed you will move from x1/y1 to x2/y2.

NOTE : For speed, 0 is instant, 1000 is dirt slow (so it's the opposite of real speed), the speeds cannot be the same or the client crashes .

The direction variable should be self explanatory, it's the direction your character faces, it can be between 0 to 3 (inclusive)..but i didn't test those yet 
	 */
	
	/**
	 * Appends an animation update.
	 * @param block The update block.
	 * @param otherPlayer The player.
	 */
	private void appendAnimationUpdate(PacketBuilder block, Player otherPlayer) {
		block.putShort(otherPlayer.getCurrentAnimation().getId());
		block.putByteS((byte) otherPlayer.getCurrentAnimation().getDelay());
	}

	/**
	 * Appends a graphics update.
	 * @param block The update block.
	 * @param otherPlayer The player.
	 */
	private void appendGraphicsUpdate(PacketBuilder block, Player otherPlayer) {
		block.putLEShort(otherPlayer.getCurrentGraphic().getId());
		block.putInt2(otherPlayer.getCurrentGraphic().getDelay());
	}

	/**
	 * Appends a chat text update.
	 * @param packet The packet.
	 * @param otherPlayer The player.
	 */
	private void appendChatUpdate(PacketBuilder block, Player otherPlayer) {
		/*ChatMessage cm = otherPlayer.getCurrentChatMessage();
		
		byte[] bytes = cm.getText();
		
		packet.putLEShort(((cm.getColour() & 0xFF) << 8) | (cm.getEffects() & 0xFF));
		packet.put((byte) otherPlayer.getRights().toInteger());
		packet.putByteC(bytes.length);
		for(int ptr = bytes.length -1; ptr >= 0; ptr--) {
			packet.put(bytes[ptr]);
		}*/
		ChatMessage cm = otherPlayer.getCurrentChatMessage();
		block.putShortA(cm.getEffects());
		block.putByteC(otherPlayer.getRights().toInteger());
		byte[] chatStr = new byte[256];
		chatStr[0] = (byte) cm.getChatText().length();
		int offset = 1 + TextUtils.encryptPlayerChat(chatStr, 0, 1, cm.getChatText().length(), cm.getChatText().getBytes());
		block.put((byte) offset);
		block.putBytesA(chatStr, 0, offset);
	}

	/**
	 * Appends an appearance update.
	 * @param packet The packet.
	 * @param otherPlayer The player.
	 */
	private void appendPlayerAppearanceUpdate(PacketBuilder packet, Player otherPlayer) {
		Appearance app = otherPlayer.getAppearance();
		Container eq = otherPlayer.getEquipment();
		
		PacketBuilder playerProps = new PacketBuilder();
		playerProps.put((byte) app.getGender()); // gender
		playerProps.put((byte) otherPlayer.getSkulls().getSkullId()); // skull icon
		playerProps.put((byte) otherPlayer.getPrayer().getPrayerIcon()); // prayer icon
		
		for(int i = 0; i < 4; i++) {
			if(eq.isSlotUsed(i)) {
				playerProps.putShort((short) 512 + eq.get(i).getId());
			} else {
				playerProps.put((byte) 0);
			}
		}
		if(eq.isSlotUsed(Equipment.SLOT_CHEST)) {
			playerProps.putShort((short) 512 + eq.get(Equipment.SLOT_CHEST).getId());
		} else {
			playerProps.putShort((short) 0x100 + app.getChest()); // chest
		}
		if(eq.isSlotUsed(Equipment.SLOT_SHIELD)){
			playerProps.putShort((short) 512 + eq.get(Equipment.SLOT_SHIELD).getId());
		} else {
			playerProps.put((byte) 0);
		}
		Item chest = eq.get(Equipment.SLOT_CHEST);
		if(chest != null) {
			if(!Equipment.is(EquipmentType.PLATEBODY, chest)) {
				playerProps.putShort((short) 0x100 + app.getArms());
			} else {
				playerProps.putShort((short) 512 + chest.getId());
			}
		} else {
			playerProps.putShort((short) 0x100 + app.getArms());
		}
		if(eq.isSlotUsed(Equipment.SLOT_BOTTOMS)) {
			playerProps.putShort((short) 512 + eq.get(Equipment.SLOT_BOTTOMS).getId());
		} else {
			playerProps.putShort((short) 0x100 + app.getLegs());
		}
		Item helm = eq.get(Equipment.SLOT_HELM);
		if(helm != null) {
			if(!Equipment.is(EquipmentType.FULL_HELM, helm) && !Equipment.is(EquipmentType.FULL_MASK, helm)) {
				playerProps.putShort((short) 0x100 + app.getHead());
			} else {
				playerProps.put((byte) 0);
			}
		} else {
			playerProps.putShort((short) 0x100 + app.getHead());
		}
		if(eq.isSlotUsed(Equipment.SLOT_GLOVES)) {
			playerProps.putShort((short) 512 + eq.get(Equipment.SLOT_GLOVES).getId());
		} else {
			playerProps.putShort((short) 0x100 + app.getHands());
		}
		if(eq.isSlotUsed(Equipment.SLOT_BOOTS)) {
			playerProps.putShort((short) 512 + eq.get(Equipment.SLOT_BOOTS).getId());
		} else {
			playerProps.putShort((short) 0x100 + app.getFeet());
		}
		boolean fullHelm = false;
		if(helm != null) {
			fullHelm = !Equipment.is(EquipmentType.FULL_HELM, helm);
		}
		if(fullHelm || app.getGender() == 1) {
			playerProps.put((byte) 0);
		} else {
			playerProps.putShort((short) 0x100 + app.getBeard());
		}
		
		playerProps.put((byte) app.getHairColour()); // hairc
		playerProps.put((byte) app.getTorsoColour()); // torsoc
		playerProps.put((byte) app.getLegColour()); // legc
		playerProps.put((byte) app.getFeetColour()); // feetc
		playerProps.put((byte) app.getSkinColour()); // skinc
		
		Item weapon = eq.get(Equipment.SLOT_WEAPON);
		if(player.differentUpdateAnimations()) {
			playerProps.putShort((short) player.getTemporaryUpdatingAnimation(0)); // stand
			playerProps.putShort((short) player.getTemporaryUpdatingAnimation(1)); // stand turn
			playerProps.putShort((short) player.getTemporaryUpdatingAnimation(2)); // walk
			playerProps.putShort((short) player.getTemporaryUpdatingAnimation(3)); // turn 180
			playerProps.putShort((short) player.getTemporaryUpdatingAnimation(4)); // turn 90 cw
			playerProps.putShort((short) player.getTemporaryUpdatingAnimation(5)); // turn 90 ccw
			playerProps.putShort((short) player.getTemporaryUpdatingAnimation(6)); // run
		} else {
			playerProps.putShort((short) Equipment.getStandAnim(weapon)); // stand
			playerProps.putShort((short) 0x337); // stand turn
			playerProps.putShort((short) Equipment.getWalkAnim(weapon)); // walk
			playerProps.putShort((short) 0x334); // turn 180
			playerProps.putShort((short) 0x335); // turn 90 cw
			playerProps.putShort((short) 0x336); // turn 90 ccw
			playerProps.putShort((short) Equipment.getRunAnim(weapon)); // run
		}		
		playerProps.putLong(otherPlayer.getNameAsLong()); // player name
		playerProps.put((byte) otherPlayer.getSkills().getCombatLevel()); // combat level
		playerProps.putShort(0); // (skill-level instead of combat-level) otherPlayer.getSkills().getTotalLevel()); // total level
		
		Packet propsPacket = playerProps.toPacket();
		
		packet.put((byte) (propsPacket.getLength() & 0xff));
		packet.putBytesA(propsPacket.getPayload(), 0, propsPacket.getLength());
		/*packet.putByteC(propsPacket.getLength());
		packet.put(propsPacket.getPayload());*/
	}

	/**
	 * Updates this player's movement.
	 * @param packet The packet.
	 */
	public void updateThisPlayerMovement(PacketBuilder packet) {
		/*
		 * Check if the player is teleporting.
		 */
		if(player.isTeleporting() || player.isMapRegionChanging()) {			
			/*
			 * They are, so an update is required.
			 */
			packet.putBits(1, 1);
			
			/*
			 * This value indicates the player teleported.
			 */
			packet.putBits(2, 3);
			
			/*
			 * This indicates that the client should discard the walking queue.
			 */
			packet.putBits(1, 1);
			
			/*
			 * This is the new player height.
			 */
			packet.putBits(2, player.getLocation().getZ());
			
			/*
			 * These are the positions.
			 */
			packet.putBits(7, player.getLocation().getLocalY(player.getLastKnownRegion()));
			packet.putBits(7, player.getLocation().getLocalX(player.getLastKnownRegion()));
			
			/*
			 * This flag indicates if an update block is appended.
			 */
			packet.putBits(1, player.getUpdateFlags().isUpdateRequired() ? 1 : 0);
		} else {
			/*
			 * Otherwise, check if the player moved.
			 */
			if(player.getSprites().getPrimarySprite() == -1) {
				/*
				 * The player didn't move. Check if an update is required.
				 */
				if(player.getUpdateFlags().isUpdateRequired()) {
					/*
					 * Signifies an update is required.
					 */
					packet.putBits(1, 1);
					
					/*
					 * But signifies that we didn't move.
					 */
					packet.putBits(2, 0);
				} else {
					/*
					 * Signifies that nothing changed.
					 */
					packet.putBits(1, 0);
				}
			} else {
				/*
				 * Check if the player was running.
				 */
				if(player.getSprites().getSecondarySprite() == -1) {
					/*
					 * The player walked, an update is required.
					 */
					packet.putBits(1, 1);
					
					/*
					 * This indicates the player only walked.
					 */
					packet.putBits(2, 1);
					
					/*
					 * This is the player's walking direction.
					 */
					packet.putBits(3, player.getSprites().getPrimarySprite());
					
					/*
					 * This flag indicates an update block is appended.
					 */
					packet.putBits(1, player.getUpdateFlags().isUpdateRequired() ? 1 : 0);
				} else {
					/*
					 * The player ran, so an update is required.
					 */
					packet.putBits(1, 1);
					
					/*
					 * This indicates the player ran.
					 */
					packet.putBits(2, 2);
					
					/*
					 * This is the walking direction.
					 */
					packet.putBits(3, player.getSprites().getPrimarySprite());
					
					/*
					 * And this is the running direction.
					 */
					packet.putBits(3, player.getSprites().getSecondarySprite());
					
					/*
					 * And this flag indicates an update block is appended.
					 */
					packet.putBits(1, player.getUpdateFlags().isUpdateRequired() ? 1 : 0);
				}
			}
		}
	}

}
